package com.xinqi.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xinqi.dto.Result;
import com.xinqi.entity.Blog;
import com.xinqi.entity.User;
import com.xinqi.mapper.BlogMapper;
import com.xinqi.service.BlogService;
import com.xinqi.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;
import redis.clients.jedis.*;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

@Slf4j
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements BlogService {
    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Override
    public List<Blog> getPageByLimited(Page<Blog> pageParam) {
        Page<Blog> blogPage = baseMapper.selectPage(pageParam, null);
        List<Blog> records = blogPage.getRecords();
        return records;
    }

    @Override
    public Integer queryBlogLikesNumber(Long id) {
        String key = "BLOG_LIKED_KEY" + id;
        Set<String> set = stringRedisTemplate.opsForZSet().range(key, 0, -1);
        Integer likeNumber = 0;
        if (set != null) likeNumber = set.size();
        return likeNumber;
    }//这里的数据持久化就由定时任务去执行吧

    @Override
    public Result likeBlog(Long blogId, Long userId) {

        String key = "BLOG_LIKED_KEY" + blogId;
        Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
        if (score == null) {
            //如果未点赞，可以点赞
            // 保存用户到Redis的set集合
            stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
        } else {
            //如果已点赞，取消点赞
            //把用户从Redis的set集合移除
            stringRedisTemplate.opsForZSet().remove(key, userId.toString());
        }
        return Result.ok();
    }

    //在redis里查询该博客下所有点赞的用户ids
    @Override
    public List<Long> queryAllBlogLikesUserIds(Long id) {
        String key = "BLOG_LIKED_KEY" + id;
        return queryAllBlogLikesUserIdsByRedisKey(key);
    }

    private List<Long> queryAllBlogLikesUserIdsByRedisKey(String key) {
        Set<String> stringUserList = stringRedisTemplate.opsForZSet().range(key, 0, -1);
        if (stringUserList == null || stringUserList.isEmpty()) return Collections.emptyList();

        List<Long> ids = stringUserList.stream().map(Long::valueOf).collect(Collectors.toList());

        return ids;
    }

    @Autowired
    private UserService userService;

    //在redis里查询该篇博客里所有点赞的用户users
    @Override
    public List<User> getAllBlogLikesUsersByBlogId(Long id) {
        List<Long> ids = queryAllBlogLikesUserIds(id);

        String idStr = StrUtil.join(",", ids);
        // 根据用户id查询用户 WHERE id IN ( 5 , 1 ) ORDER BY FIELD(id, 5, 1)
        List<User> users = new ArrayList<>(userService.query()
                .in("id", ids).last("ORDER BY FIELD(id," + idStr + ")")
                .list());
        return users;
    }

    //查询redis里的likeList，以Map<String,String> 返回
    @Override
    public Map<String, String> queryAllBlogLikesUserIdWithScores(Long id) {
        String key = "BLOG_LIKED_KEY" + id;
        return queryAllBlogLikesUserIdWithScoresByRedisKey(key);
    }

    private Map<String, String> queryAllBlogLikesUserIdWithScoresByRedisKey(String key) {
        Set<String> stringUserIdList = stringRedisTemplate.opsForZSet().range(key, 0, -1);

        if (stringUserIdList == null || stringUserIdList.size() == 0) return new HashMap<>();

        List<String> ids = stringUserIdList.stream().map(String::valueOf).collect(Collectors.toList());
        HashMap<String, String> userIdWithTime = new HashMap<>();
        for (String userId : stringUserIdList) {//暂时找不到可以直接返回List<Double>的api
            String score = stringRedisTemplate.opsForZSet().score(key, userId).toString();
            userIdWithTime.put(userId, score);
        }
        return userIdWithTime;
    }

    //持久化redis里的某篇博客的likeList到数据库
    @Override
    public void durableForLikeListByBogId(Long id) {
        Map<String, String> userIdWithTime = queryAllBlogLikesUserIdWithScores(id);
        durableToDatabaseByBlogIdAndMap(userIdWithTime, id);

    }

    private void durableToDatabaseByBlogIdAndMap(Map<String, String> userIdWithTime, Long id) {
        //什么都没有那肯定不操作
        if (userIdWithTime == null || userIdWithTime.size() == 0) return;

        String stringIds = userIdWithTime.toString();

        String updateSql = "like_list = " + "'" + stringIds + "' ," +
                "like_number = " + userIdWithTime.size();
        boolean isSuccess = update()
                .setSql(updateSql)
                .eq("id", id)
                .update();
    }

    //将数据库里的likeList拿出来
    @Override
    public HashMap<String, String> getIdsInTableByBlogId(Long id) {
        Blog blog = getById(id);
        String likeList = blog.getLikeList();


        HashMap<String, String> hashMap = (HashMap<String, String>) JSONUtil.toBean(likeList, Map.class);

        return hashMap;
    }


    //这里就执行所有博客的likeList入库吧
    @Override
    public Result updateAllLikeListToDatabase() {
        String prefix = "BLOG_LIKED_KEY";
        Set<String> keys = getAllRedisKeyByScanThroughMatch(prefix);

        if (keys == null || keys.size() == 0) return Result.ok("there is not any like_lists in blogs");
        for (String key : keys) {
            Map<String, String> map = queryAllBlogLikesUserIdWithScoresByRedisKey(key);
            //get blogId，，这个blogId是在后半段的。
            Long blogId = Long.parseLong(key.substring(prefix.length(), key.length()));

            durableToDatabaseByBlogIdAndMap(map, blogId);

        }

        return Result.ok("some like_lists had bean durable in database from cache");

    }

    //通过scan来模糊匹配
    public Set<String> getAllRedisKeyByScanThroughMatch(String prefix) {//找不到stringRedisTemplate对Zset里键值对扫描的资料
        Jedis jedis = null;
        Set<String> blogLikeList = new HashSet<>();
        ScanParams scanParams = new ScanParams();
        try {
            jedis = jedisPool.getResource();
            String cursor = "0";


            do {
                // 扫描并获取一部分key
                ScanResult<String> result = jedis.scan(cursor, scanParams.match(prefix.concat("*")));//https://www.cnblogs.com/xubiao/p/8489522.html
                // 记录cursor
                cursor = result.getCursor();
                List<String> list = result.getResult();
                if (list == null || list.isEmpty()) {
                    break;
                }

                // 遍历
                for (String key : list) {
                    // 判断key的类型
                    log.debug("key is {}", key);//这里可以看到有哪些key
                    //符合就要，不符合就不要
                    blogLikeList.add(key);
                }

            } while (!cursor.equals("0"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) jedis.close();
        }
        return blogLikeList;
    }


    //这里就执行所有博客的likeList入库吧，这里使用了join,算是半异步了吧
    @Override
    public Result updateAllLikeListToDatabaseByThreads() {
        int totalCount = 0;
        String prefix = "BLOG_LIKED_KEY";
        Jedis jedis = null;
        ScanParams scanParams = new ScanParams();
        try {
            jedis = jedisPool.getResource();
            String cursor = "0";


            do {
                // 扫描并获取一部分key
                ScanResult<String> result = jedis.scan(cursor, scanParams.match(prefix.concat("*")));//https://www.cnblogs.com/xubiao/p/8489522.html
                // 记录cursor
                cursor = result.getCursor();
                List<String> list = result.getResult();
                totalCount += list.size();
                if (list == null || list.isEmpty()) {
                    break;
                }

//                List<Thread> threads = new Vector<>(list.size());//对于线程的添加，这里不需要用Vector，因为临界区仅仅包括线程里面的内容
                List<Thread> threads = new ArrayList<>(list.size());//用ArrayList足以
                // 遍历
                for (String key : list) {
                    log.debug("key is {}", key);//这里可以看到有哪些key

                    Thread t = new Thread(() -> {
                        Map<String, String> map = queryAllBlogLikesUserIdWithScoresByRedisKey(key);
                        Long blogId = Long.parseLong(key.substring(prefix.length(), key.length()));
                        durableToDatabaseByBlogIdAndMap(map, blogId);
                    });//这里是和like05不一样的地方 TODO postman已经测试通过了，还没有对jemeter进行测试
                    // 判断key的类型

                    t.start();
                    threads.add(t);
                }

                threads.forEach((t) -> {
                    try {
                        t.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });

                threads = null;
            } while (!cursor.equals("0"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) jedis.close();
        }
        log.debug("totalCount is {}", totalCount);
        return Result.ok("some like_lists had bean durable in database from cache");

    }

    //异步，这里没join,应该算是异步了
    @Override
    public Result updateAllLikeListToDatabaseByThreadsWithNoJoin() {
        int totalCount = 0;
        String prefix = "BLOG_LIKED_KEY";
        Jedis jedis = null;
        ScanParams scanParams = new ScanParams();
        try {
            jedis = jedisPool.getResource();
            String cursor = "0";


            do {
                // 扫描并获取一部分key
                ScanResult<String> result = jedis.scan(cursor, scanParams.match(prefix.concat("*")));//https://www.cnblogs.com/xubiao/p/8489522.html
                // 记录cursor
                cursor = result.getCursor();
                List<String> list = result.getResult();
                totalCount += list.size();
                if (list == null || list.isEmpty()) {
                    break;
                }

                // 遍历
                for (String key : list) {
                    log.debug("key is {}", key);//这里可以看到有哪些key

                    new Thread(() -> {
                        Map<String, String> map = queryAllBlogLikesUserIdWithScoresByRedisKey(key);
                        Long blogId = Long.parseLong(key.substring(prefix.length(), key.length()));
                        durableToDatabaseByBlogIdAndMap(map, blogId);
                    }).start();//这里是和like05不一样的地方 TODO postman已经测试通过了，还没有对jemeter进行测试
                    // 判断key的类型

                }
            } while (!cursor.equals("0"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) jedis.close();
        }
        log.debug("totalCount is {}", totalCount);
        return Result.ok("some like_lists had bean durable in database from cache");

    }


    @Override//单线程，效率低
    public Result updateAllLikeListToDatabaseByNoThreads() {
        int totalCount = 0;
        String prefix = "BLOG_LIKED_KEY";
        Jedis jedis = null;
        ScanParams scanParams = new ScanParams();
        try {
            jedis = jedisPool.getResource();
            String cursor = "0";


            do {
                // 扫描并获取一部分key
                ScanResult<String> result = jedis.scan(cursor, scanParams.match(prefix.concat("*")));//https://www.cnblogs.com/xubiao/p/8489522.html
                // 记录cursor
                cursor = result.getCursor();
                List<String> list = result.getResult();
                totalCount += list.size();
                if (list == null || list.isEmpty()) {
                    break;
                }

                // 遍历
                for (String key : list) {
                    log.debug("key is {}", key);//这里可以看到有哪些key

                    Map<String, String> map = queryAllBlogLikesUserIdWithScoresByRedisKey(key);
                    Long blogId = Long.parseLong(key.substring(prefix.length(), key.length()));
                    durableToDatabaseByBlogIdAndMap(map, blogId);

                }
            } while (!cursor.equals("0"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) jedis.close();
        }
        log.debug("totalCount is {}", totalCount);
        return Result.ok("some like_lists had bean durable in database from cache");

    }

    @Autowired
    private JedisPool jedisPool;


    //测试过了，应该没问题
    public Set<String> getAllRedisKeyByScan(String prefix) {//找不到stringRedisTemplate对Zset里键值对扫描的资料
        //这里就直接用其他的Redis接口吧。

        //接下来是scan模糊扫描，获取目标数据
        //RedisTemplate这里搞不定。只能找其他的了

        Jedis jedis = null;
        Set<String> blogLikeList = new HashSet<>();
        try {
            jedis = jedisPool.getResource();
            String cursor = "0";

            do {
                // 扫描并获取一部分key
                ScanResult<String> result = jedis.scan(cursor);
                // 记录cursor
                cursor = result.getCursor();
                List<String> list = result.getResult();
                if (list == null || list.isEmpty()) {
                    break;
                }
                // 遍历
                for (String key : list) {
                    // 判断key的类型
                    String type = jedis.type(key);
                    if (type.equals("zset")) {
                        log.debug("key is {}", key);//这里可以看到有哪些key
                        //符合就要，不符合就不要
                        if (key.substring(0, prefix.length()).equals(prefix)) blogLikeList.add(key);
                    }

                }
            } while (!cursor.equals("0"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) jedis.close();
        }
        return blogLikeList;
    }


    private Set<String> getAllRedisKeyByKeys(String prefix) {
        Set<String> keys = stringRedisTemplate.keys(prefix.concat("*"));
        return keys;
    }

    @Resource
    private ThreadPoolExecutor dtpExecutor1;

    @Override
    public Result testDynamicTp( Long number) {//跑不通 TODO
        dtpExecutor1.execute(() -> log.debug("{},test" , 13 / number));
//        org.dromara.dynamictp
        return Result.ok();
    }


}
